
#include "process.h"
#include "cpu.h"

process_info *ps_working_list, *ps_record_list; // All processes
static int ps_working_cnt, ps_record_cnt;

process_info *ps_self_working, *ps_self_record;

process_info_ptr *ps_working_ptr, *ps_record_ptr; // With out system processes
static int ps_working_ptr_cnt, ps_record_ptr_cnt;

static int ps_working_reserved_size;
static int ps_no_system_process;
unsigned long ps_total_delta_time;

char self_path[BUFFERSIZE];
int self;

void ps_self_dump();
void ps_self_refresh();

void empty_ps_list(process_info *list)
{
    if (list == NULL) return;
    free(list);
}

void ps_cleanup()
{
    /*
    empty_ps_list(ps_working_list);
    empty_ps_list(ps_record_list);
    if (ps_working_ptr) free(ps_working_ptr);
    if (ps_record_ptr) free(ps_record_ptr);
    */
    if (ps_self_working) free(ps_self_working);
    if (ps_self_record) free(ps_self_record);
}

int is_ps_list_empty()
{
    return ps_record_cnt == 0;
}

void init_process()
{
    self = getpid();
    snprintf(self_path, BUFFERSIZE, "/proc/%d/stat", self);
    ps_self_working = (process_info*)malloc(sizeof(process_info));
    ps_self_record  = (process_info*)malloc(sizeof(process_info));

    ps_self_dump();
    ps_self_refresh();
}

void ps_self_dump()
{
    FILE *ps;
    ps = fopen(self_path, "r");
    if (ps == NULL) {
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
                "process %s is NULL, now return\n", self_path);
        return;
    }

    fscanf(ps, "%d (%s %c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d "
               "%lu %lu %*d %*d %*d %*d %d %*d %*d %*lu %ld",
               &ps_self_working->pid, ps_self_working->name, &ps_self_working->status,
               &ps_self_working->delta_utime, &ps_self_working->delta_stime,
               &ps_self_working->threadnum, &ps_self_working->rss);
    fclose(ps);
    ps_self_working->name[strlen(ps_self_working->name) - 1] = 0;
}

void ps_self_refresh()
{
    unsigned long delta_stime = ps_self_working->delta_stime - ps_self_record->delta_stime;
    unsigned long delta_utime = ps_self_working->delta_utime - ps_self_record->delta_utime;
    ps_self_working->load = (delta_stime + delta_utime) * 100 / total_delta_time;
    swap_ptr((void*)&ps_self_working, (void*)&ps_self_record);
}

int get_ps_self_load()
{
    return ps_self_record->load;
}

////////////////////////////////////////////////////////////////////////////////
/*
 * Add all processes except systems to ps_working_ptr
 *//*
void ps_add_list(process_info *new)
{
    process_info *new_ps = ps_working_list + ps_working_cnt;
    memcpy(new_ps, new, sizeof(process_info));
    ++ps_working_cnt;
    ps_working_ptr[ps_working_ptr_cnt++] = new_ps;
    //__android_log_print(ANDROID_LOG_INFO, LOG_TAG,
    //                    "ps_add_list: %d %s\n",
    //                    ps_working_ptr[ps_working_ptr_cnt-1]->pid,
    //                    ps_working_ptr[ps_working_ptr_cnt-1]->name);
}

/*
 * Add system processes to ps_working_list
 *//*
void ps_add_system(process_info *sys)
{
    process_info *sys_ps = ps_working_list + ps_working_cnt;
    memcpy(sys_ps, sys, sizeof(process_info));
    ++ps_working_cnt;
    //__android_log_print(ANDROID_LOG_INFO, LOG_TAG,
    //                    "ps_add_system: %d %s\n",
    //                    ps_working_list[ps_working_cnt-1].pid,
    //                    ps_working_list[ps_working_cnt-1].name);
}
/*
void sort(int keyword)
{

}

void ps_instance_dump(int pid)
{
    char statline[BUFFERSIZE * 2];
    struct stat stats;
    process_info ps_info;
    FILE *ps;
    struct passwd *pw;
    
    memset(&ps_info, 0, sizeof(process_info));
    
    ps_info.pid = pid;
    snprintf(statline, BUFFERSIZE*2, "/proc/%d", pid);
    stat(statline, &stats);
    
    ps_info.uid = (int) stats.st_uid;
    
    pw = getpwuid(stats.st_uid);
    if (pw == 0) 
        snprintf(ps_info.owner, 80, "%d", (int)stats.st_uid);
    else
        strncpy(ps_info.owner, pw->pw_name, 80);
    
    snprintf(statline, BUFFERSIZE*2, "/proc/%d/stat", pid);

    ps = fopen(statline, "r");
    if (ps == NULL) {
        __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
                            "process %s is NULL, now return\n", statline);
        return;
    }
    //__android_log_print(ANDROID_LOG_INFO, LOG_TAG,
    //                            "reading %s\n", statline);
    fscanf(ps, "%d (%s %c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d "
               "%lu %lu %*d %*d %*d %*d %d %*d %*d %*lu %ld",
               &pid, ps_info.name, &ps_info.status,
               &ps_info.delta_utime, &ps_info.delta_stime,
               &ps_info.threadnum, &ps_info.rss);
    fclose(ps);

    // Remove the right parenthese
    ps_info.name[strlen(ps_info.name) - 1] = 0;

    // Filter system processes
    if(ps_no_system_process) {
        if((strcmp(ps_info.owner, "root") != 0) &&
           (strstr(ps_info.name, "/system/") == 0) &&
           (strstr(ps_info.name, "/sbin/") == 0)) {
            ps_add_list(&ps_info);
        } else {
            ps_add_system(&ps_info);
        }
    } else {
        ps_add_list(&ps_info);
    }
}
/*
void ps_dump()
{
    DIR *d;
    struct dirent *de;
    
    // Get the number of processes beforehand
    d = opendir(PROC);
    if (d == 0) return;
    ps_working_reserved_size = 0;
    while ((de = readdir(d)) != 0) {
        if (isdigit(de->d_name[0]))
            ++ps_working_reserved_size;
    }
    closedir(d);
    //__android_log_print(ANDROID_LOG_INFO, LOG_TAG,
    //                   "ps_working_reserved_size %d\nps_working_cnt %d\n",
    //                   ps_working_reserved_size, ps_working_cnt);
    // If the number of processes increased, then create a larger new list,
    // otherwise use the existing working_list
    if (ps_working_reserved_size > ps_working_cnt) {
        if (ps_working_list) { // reset working_list
            free(ps_working_list);
        }
        ps_working_list = (process_info *)malloc
                (sizeof(process_info) * (ps_working_reserved_size + RESERVED_SLOTS));
        if (ps_working_ptr) {
            free(ps_working_ptr);
        }
        ps_working_ptr = (process_info_ptr*)malloc
                (sizeof(process_info_ptr) * (ps_working_reserved_size + RESERVED_SLOTS));
    }

    // Reset the counter before updating the list
    ps_working_cnt = 0;
    ps_working_ptr_cnt = 0;

    d = opendir(PROC);
    if (d == 0) return;
    while ((de = readdir(d)) != 0) {
        if (isdigit(de->d_name[0])) {
            int pid = atoi(de->d_name);
            ps_instance_dump(pid);
        }
    }
    closedir(d);
}
/*
void refresh_load()
{
    int i, j;
    ps_total_delta_time = 0;
    int cpu = 0;
    process_info_ptr pti, ptj;
    unsigned long divide_total_time = total_delta_time;
    for (i=0; i<ps_working_cnt; ++i) {
        pti = ps_working_list + i;
        pti->load = 0;
        for (j=0; j<ps_record_cnt; ++j) {
            ptj = ps_record_list + j;
            if (pti->pid == ptj->pid) {
                unsigned long delta_stime = pti->delta_stime - ptj->delta_stime;
                unsigned long delta_utime = pti->delta_utime - ptj->delta_utime;
                pti->load = delta_stime + delta_utime;
                ps_total_delta_time += pti->load;
                break;
            }
        }
    }
    if (ps_total_delta_time > divide_total_time)
        divide_total_time = ps_total_delta_time;
    //__android_log_print(ANDROID_LOG_INFO, LOG_TAG,
    //                    "ps_total_delta_time %u\n", ps_total_delta_time);
    for (i=0; i<ps_working_cnt; ++i) {
        int *pt = &ps_working_list[i].load;
        *pt = *pt * 100 / divide_total_time;
        //__android_log_print(ANDROID_LOG_INFO, LOG_TAG,
        //                    "ps name %s load %d\n", ps_working_list[i].name, ps_working_list[i].load);
        if (*pt > 100 || *pt < 0)
            *pt = 0;
        cpu += *pt;
    }
    cpu_usage = cpu;
}

void refresh_process()
{
    refresh_load();

    // not atomic, potential bug
    swap_int(&ps_record_ptr_cnt, &ps_working_ptr_cnt);
    swap_ptr((void*)&ps_working_ptr, (void*)&ps_record_ptr);

    swap_int(&ps_record_cnt, &ps_working_cnt);
    swap_ptr((void*)&ps_working_list, (void*)&ps_record_list);
}
*/

void ps_set_no_system_process(int noSystem)
{
    ps_no_system_process = noSystem;
}

int index_inside(int id)
{
    return (0 <= id && id < ps_record_ptr_cnt);
}

int get_ps_counts()
{
    return ps_record_ptr_cnt;
}

int get_pid_by_index(int index)
{
    if (index_inside(index)) {
        return ps_record_ptr[index]->pid;
    }
    return -1;
}

int get_uid_by_index(int index)
{
    if (index_inside(index)) {
        return ps_record_ptr[index]->uid;
    }
    return 0;
}

unsigned long get_stime_by_index(int index)
{
    if (index_inside(index)) {
        return ps_record_ptr[index]->delta_stime;
    }
    return 0;
}

unsigned long get_utime_by_index(int index)
{
    if (index_inside(index)) {
        return ps_record_ptr[index]->delta_utime;
    }
    return 0;
}

int get_rss_by_index(int index)
{
    if (index_inside(index)) {
        return ps_record_ptr[index]->rss;
    }
    return 0;
    
}

int get_threads_by_index(int index)
{
    if (index_inside(index)) {
        return ps_record_ptr[index]->threadnum;
    }
    return 0;
}

void get_name_by_index(int index, char *buf)
{
    buf[0] = 0;
    if (index_inside(index)) {
        strncpy(buf, ps_record_ptr[index]->name, BUFFERSIZE);
    }
}

void get_owner_by_index(int index, char *buf)
{
    buf[0] = 0;
    if (index_inside(index)) {
        strncpy(buf, ps_record_ptr[index]->owner, BUFFERSIZE);
    }
}

int get_load_by_index(int index)
{
    if (index_inside(index)) {
        return (int)ps_record_ptr[index]->load;
    }
    return 0;
}

// untested
void get_status_by_index(int index, char *c)
{
    c[0] = 0;
    if (index_inside(index)) {
        snprintf(c, BUFFERSIZE, "%c", ps_record_ptr[index]->status);
    }
}

// TODO
void get_name_by_uid(int uid, char *buf)
{
     buf[0] = 0;
     return;
}

